package com.sixbro.resource.component;

import com.google.common.collect.ImmutableSet;
import org.reflections.Reflections;
import org.reflections.scanners.MethodAnnotationsScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author: Mr.Lu
 * @since: 2021/11/2 16:35
 */
public class AntMatchersFactory {
    private static final String ALL = "ALL";
    private static final String SHARP = "#";
    private static final String SPACE = " ";
    private static final String EMPTY_STRING = "";
    private static final String PACKAGE = "com.bossdream";
    private static final String PERMIT_ALL = "permitAll";
    private static Set< String > antMatchers = new LinkedHashSet<>( );

    public static String[] makeRequestMappings( ) {
        shouldBeInitialization( );
        return antMatchers.stream( )
                .filter( matcher -> matcher.startsWith( ALL ) )
                .map( matcher -> matcher.replace( ALL, EMPTY_STRING ).trim( ) )
                .collect( Collectors.toSet( ) ).toArray( String[]::new );
    }

    public static String[] makeGetMappings( ) {
        shouldBeInitialization( );
        return antMatchers.stream( )
                .filter( matcher -> matcher.startsWith( HttpMethod.GET.name( ) ) )
                .map( matcher -> matcher.replace( HttpMethod.GET.name( ), EMPTY_STRING ).trim( ) )
                .collect( Collectors.toSet( ) ).toArray( String[]::new );
    }

    public static String[] makePostMappings( ) {
        shouldBeInitialization( );
        return antMatchers.stream( )
                .filter( matcher -> matcher.startsWith( HttpMethod.POST.name( ) ) )
                .map( matcher -> matcher.replace( HttpMethod.POST.name( ), EMPTY_STRING ).trim( ) )
                .collect( Collectors.toSet( ) ).toArray( String[]::new );
    }

    public static String[] makePutMappings( ) {
        shouldBeInitialization( );
        return antMatchers.stream( )
                .filter( matcher -> matcher.startsWith( HttpMethod.PUT.name( ) ) )
                .map( matcher -> matcher.replace( HttpMethod.PUT.name( ), EMPTY_STRING ).trim( ) )
                .collect( Collectors.toSet( ) ).toArray( String[]::new );
    }

    public static String[] makeDeleteMappings( ) {
        shouldBeInitialization( );
        return antMatchers.stream( )
                .filter( matcher -> matcher.startsWith( HttpMethod.DELETE.name( ) ) )
                .map( matcher -> matcher.replace( HttpMethod.DELETE.name( ), EMPTY_STRING ).trim( ) )
                .collect( Collectors.toSet( ) ).toArray( String[]::new );
    }

    public static String[] makePatchMappings( ) {
        shouldBeInitialization( );
        return antMatchers.stream( )
                .filter( matcher -> matcher.startsWith( HttpMethod.PATCH.name( ) ) )
                .map( matcher -> matcher.replace( HttpMethod.PATCH.name( ), EMPTY_STRING ).trim( ) )
                .collect( Collectors.toSet( ) ).toArray( String[]::new );
    }

    private static void shouldBeInitialization( ) {
        if ( antMatchers.isEmpty( ) ) {
            antMatchers = initialization( );
        }
    }

    private static Set< String > initialization( ) {
        antMatchers.add( "ALL /actuator/**" );
        Reflections reflections = new Reflections(
                new ConfigurationBuilder( )
                        .setUrls(ClasspathHelper.forPackage( PACKAGE ) )
                        .setScanners(new MethodAnnotationsScanner( ), new TypeAnnotationsScanner( ), new SubTypesScanner( ) )
        );

        reflections.getTypesAnnotatedWith( PreAuthorize.class ).stream( ).filter(type ->
                type.getAnnotation( PreAuthorize.class ).value( ).contains( PERMIT_ALL )
        ).collect( Collectors.toSet( ) ).forEach( AntMatchersFactory::typeAntMatchers );
        reflections.getTypesAnnotatedWith( PostAuthorize.class ).stream( ).filter(type ->
                type.getAnnotation( PostAuthorize.class ).value( ).contains( PERMIT_ALL )
        ).collect( Collectors.toSet( ) ).forEach( AntMatchersFactory::typeAntMatchers );


        reflections.getMethodsAnnotatedWith( PreAuthorize.class ).stream( ).filter( method ->
                method.getAnnotation( PreAuthorize.class ).value( ).contains( PERMIT_ALL )
        ).collect( Collectors.toSet( ) ).forEach( AntMatchersFactory::methodAntMatchers );
        reflections.getMethodsAnnotatedWith( PostAuthorize.class ).stream( ).filter( method ->
                method.getAnnotation( PostAuthorize.class ).value( ).contains( PERMIT_ALL )
        ).collect( Collectors.toSet( ) ).forEach( AntMatchersFactory::methodAntMatchers );
        return antMatchers.stream( ).map( matcher -> matcher.endsWith( "/" ) ? matcher + "**" : matcher + "/**" ).collect( Collectors.toSet( ) );
    }

    private static void typeAntMatchers( Class< ? > type ) {
        Optional.ofNullable( type.getAnnotation( RequestMapping.class ) ).ifPresent(mapping -> {
            Set< String > paths = pathResolver( ALL, mapping.path( ) ).stream( ).map( v -> v.replace( SHARP, SPACE ) ).collect( Collectors.toSet( ) );
            Set< String > values = pathResolver( ALL, mapping.value( ) ).stream( ).map( v -> v.replace( SHARP, SPACE ) ).collect( Collectors.toSet( ) );
            antMatchers.addAll( paths );
            antMatchers.addAll( values );
        } );
    }

    private static void methodAntMatchers( Method method ) {
        Set< String > antMatchersTemporary = new LinkedHashSet<>( );
        Optional.ofNullable( method.getAnnotation( RequestMapping.class ) ).ifPresent( mapping -> {
            antMatchersTemporary.addAll( pathResolver( ALL, mapping.path( ) ) );
            antMatchersTemporary.addAll( pathResolver( ALL, mapping.value( ) ) );
        } );
        Optional.ofNullable( method.getAnnotation( GetMapping.class ) ).ifPresent(mapping -> {
            antMatchersTemporary.addAll( pathResolver( HttpMethod.GET.name( ), mapping.path( ) ) );
            antMatchersTemporary.addAll( pathResolver( HttpMethod.GET.name( ), mapping.value( ) ) );
        } );
        Optional.ofNullable( method.getAnnotation( PostMapping.class ) ).ifPresent(mapping -> {
            antMatchersTemporary.addAll( pathResolver( HttpMethod.POST.name( ), mapping.path( ) ) );
            antMatchersTemporary.addAll( pathResolver( HttpMethod.POST.name( ), mapping.value( ) ) );
        } );
        Optional.ofNullable( method.getAnnotation( PutMapping.class ) ).ifPresent(mapping -> {
            antMatchersTemporary.addAll( pathResolver( HttpMethod.PUT.name( ), mapping.path( ) ) );
            antMatchersTemporary.addAll( pathResolver( HttpMethod.PUT.name( ), mapping.value( ) ) );
        } );
        Optional.ofNullable( method.getAnnotation( DeleteMapping.class ) ).ifPresent(mapping -> {
            antMatchersTemporary.addAll( pathResolver( HttpMethod.DELETE.name( ), mapping.path( ) ) );
            antMatchersTemporary.addAll( pathResolver( HttpMethod.DELETE.name( ), mapping.value( ) ) );
        } );
        Optional.ofNullable( method.getAnnotation( PatchMapping.class ) ).ifPresent( mapping -> {
            antMatchersTemporary.addAll( pathResolver( HttpMethod.PATCH.name( ), mapping.path( ) ) );
            antMatchersTemporary.addAll( pathResolver( HttpMethod.PATCH.name( ), mapping.value( ) ) );
        } );
        RequestMapping declaringRequestMapping = method.getDeclaringClass( ).getAnnotation( RequestMapping.class );
        Set< String > paths = pathResolver( ALL, declaringRequestMapping.path( ) );
        Set< String > values = pathResolver( ALL, declaringRequestMapping.value( ) );
        values.addAll( paths );
        values = values.stream( ).map( value -> value.replace( SHARP, SPACE ) ).collect( Collectors.toSet( ) );
        values.forEach( value -> {
            for ( String temporary : antMatchersTemporary ) {
                value = temporary.replace( SHARP, value )
                        .replace( "GETGETALL", HttpMethod.GET.name( ) )
                        .replace( "GETALL", HttpMethod.GET.name( ) )
                        .replace( "GETGET", HttpMethod.GET.name( ) )

                        .replace( "POSTPOSTALL", HttpMethod.POST.name( ) )
                        .replace( "POSTALL", HttpMethod.POST.name( ) )
                        .replace( "POSTPOST", HttpMethod.POST.name( ) )

                        .replace( "PUTPUTALL", HttpMethod.PUT.name( ) )
                        .replace( "PUTALL", HttpMethod.PUT.name( ) )
                        .replace( "PUTPUT", HttpMethod.PUT.name( ) )

                        .replace( "DELETEDELETEALL", HttpMethod.DELETE.name( ) )
                        .replace( "DELETEALL", HttpMethod.DELETE.name( ) )
                        .replace( "DELETEDELETE", HttpMethod.DELETE.name( ) )

                        .replace( "PATCHPATCHALL", HttpMethod.PATCH.name( ) )
                        .replace( "PATCHALL", HttpMethod.PATCH.name( ) )
                        .replace( "PATCHPATCH", HttpMethod.PATCH.name( ) )

                        .replace( "ALLALLALL", ALL )
                        .replace( "ALLALL", ALL );
            }
            antMatchers.add( value );
        } );
    }

    private static Set< String > pathResolver( String httpMethod, String[] paths ) {
        if ( paths.length < 1 ) {
            return !httpMethod.equals( ALL ) ? ImmutableSet.of( httpMethod + SHARP ) : ImmutableSet.of( );
        }
        return Arrays.stream( paths ).map(path -> httpMethod + SHARP + path ).collect( Collectors.toSet( ) );
    }
}